"""
主题: 将装饰器定义为类
问题: 你想使用一个装饰器去包装函数，但是希望返回一个可调用的实例。 你需要让你的装饰器可以同时工作在类定义的内部和外部。
提示 : 
"""

import types
from functools import wraps

class Profiled:
    def __init__(self, func):
        wraps(func)(self)
        self.ncalls = 0

    def __call__(self, *args, **kwargs):
        self.ncalls += 1
        return self.__wrapped__(*args, **kwargs)

    def __get__(self, instance, cls):
        if instance is None:
            return self
        else:
            return types.MethodType(self, instance)

@Profiled
def add(x, y):
    return x + y

class Spam:
    @Profiled
    def bar(self, x):
        print(self, x)

def profiled(func):
    '''使用闭包和 nonlocal 变量实现的装饰器'''
    ncalls = 0
    @wraps(func)
    def wrapper(*args, **kwargs):
        nonlocal ncalls
        ncalls += 1
        return func(*args, **kwargs)
    wrapper.ncalls = lambda: ncalls
    return wrapper

# Example
@profiled
def add1(x, y):
    return x + y

def recipe1():
    print(f"{add(2, 3) }")
    print(f"{add(3, 4) }")
    print(f"{add.ncalls = }")

    s = Spam()
    print(f"{s.bar(1) = }") 
    print(f"{s.bar.ncalls = }")       

def recipe2():
    print(f"{add1(2, 3) }")
    print(f"{add1(3, 4) }")
    print(f"{add1.ncalls() = }")

def main():
    print('recipe1'.center(20, '*'))
    recipe1()
    print('recipe2'.center(20, '*'))
    recipe2()

if __name__ == '__main__':
    main()                                  